// This test is a basic sanity check of the shell helpers for manipulating role objects
// It is not a comprehensive test of the functionality of the role manipulation commands
//
// @tags: [
//   # The test runs commands that are not allowed with security token: createRole, dropRole,
//   # grantPrivilegesToRole, grantRolesToRole, revokeRolesFromRole, updateRole.
//   not_allowed_with_signed_security_token,
//   assumes_superuser_permissions,
//   assumes_write_concern_unchanged,
//   requires_auth,
//   requires_multi_updates,
//   requires_non_retryable_commands,
// ]

function assertHasRole(rolesArray, roleName, roleDB) {
    for (let i in rolesArray) {
        var curRole = rolesArray[i];
        if (curRole.role == roleName && curRole.db == roleDB) {
            return;
        }
    }
    assert(false, "role " + roleName + "@" + roleDB + " not found in array: " + tojson(rolesArray));
}

function assertHasPrivilege(privilegeArray, privilege) {
    for (let i in privilegeArray) {
        var curPriv = privilegeArray[i];
        if (curPriv.resource.cluster == privilege.resource.cluster &&
            curPriv.resource.anyResource == privilege.resource.anyResource &&
            curPriv.resource.db == privilege.resource.db &&
            curPriv.resource.collection == privilege.resource.collection) {
            // Same resource
            assert.eq(curPriv.actions.length, privilege.actions.length);
            for (let k in curPriv.actions) {
                assert.eq(curPriv.actions[k], privilege.actions[k]);
            }
            return;
        }
    }
    assert(false,
           "Privilege " + tojson(privilege) +
               " not found in privilege array: " + tojson(privilegeArray));
}

var db = db.getSiblingDB("role_management_helpers");
db.dropDatabase();
db.dropAllRoles();

db.createRole({
    role: 'roleA',
    roles: [],
    privileges: [{resource: {db: db.getName(), collection: "foo"}, actions: ['find']}]
});
db.createRole({role: 'roleB', privileges: [], roles: ["roleA"]});
db.createRole({role: 'roleC', privileges: [], roles: []});

// Test getRole
var roleObj = db.getRole("roleA");
assert.eq(0, roleObj.roles.length);
assert.eq(null, roleObj.privileges);
roleObj = db.getRole("roleA", {showPrivileges: true});
assert.eq(1, roleObj.privileges.length);
assertHasPrivilege(roleObj.privileges,
                   {resource: {db: db.getName(), collection: "foo"}, actions: ['find']});
roleObj = db.getRole("roleB", {showPrivileges: true});
assert.eq(1, roleObj.inheritedPrivileges.length);  // inherited from roleA
assertHasPrivilege(roleObj.inheritedPrivileges,
                   {resource: {db: db.getName(), collection: "foo"}, actions: ['find']});
assert.eq(1, roleObj.roles.length);
assertHasRole(roleObj.roles, "roleA", db.getName());

// Test getRoles
var roles = db.getRoles();
assert.eq(3, roles.length);
printjson(roles);
assert(roles[0].role == 'roleA' || roles[1].role == 'roleA' || roles[2].role == 'roleA');
assert(roles[0].role == 'roleB' || roles[1].role == 'roleB' || roles[2].role == 'roleB');
assert(roles[0].role == 'roleC' || roles[1].role == 'roleC' || roles[2].role == 'roleC');
assert.eq(null, roles[0].inheritedPrivileges);
var roles = db.getRoles({showPrivileges: true, showBuiltinRoles: true});
assert.eq(9, roles.length);
assert.neq(null, roles[0].inheritedPrivileges);

// Granting roles to nonexistent role fails
assert.throws(function() {
    db.grantRolesToRole("fakeRole", ['dbAdmin']);
});
// Granting roles to built-in role fails
assert.throws(function() {
    db.grantRolesToRole("readWrite", ['dbAdmin']);
});
// Granting non-existant role fails
assert.throws(function() {
    db.grantRolesToRole("roleB", ['dbAdmin', 'fakeRole']);
});

roleObj = db.getRole("roleB", {showPrivileges: true});
assert.eq(1, roleObj.inheritedPrivileges.length);
assert.eq(1, roleObj.roles.length);
assertHasRole(roleObj.roles, "roleA", db.getName());

// Granting a role you already have is no problem
db.grantRolesToRole("roleB", ['readWrite', 'roleC']);
roleObj = db.getRole("roleB", {showPrivileges: true});
assert.gt(roleObj.inheritedPrivileges.length, 1);  // Got privileges from readWrite role
assert.eq(3, roleObj.roles.length);
assertHasRole(roleObj.roles, "readWrite", db.getName());
assertHasRole(roleObj.roles, "roleA", db.getName());
assertHasRole(roleObj.roles, "roleC", db.getName());

// Revoking roles the role doesn't have is fine
db.revokeRolesFromRole("roleB", ['roleA', 'readWrite', 'dbAdmin']);
roleObj = db.getRole("roleB", {showPrivileges: true});
assert.eq(0, roleObj.inheritedPrivileges.length);
assert.eq(1, roleObj.roles.length);
assertHasRole(roleObj.roles, "roleC", db.getName());

// Privileges on the same resource get collapsed
db.grantPrivilegesToRole("roleA", [
    {resource: {db: db.getName(), collection: ""}, actions: ['dropDatabase']},
    {resource: {db: db.getName(), collection: "foo"}, actions: ['insert']}
]);
roleObj = db.getRole("roleA", {showPrivileges: true});
assert.eq(0, roleObj.roles.length);
assert.eq(2, roleObj.privileges.length);
assertHasPrivilege(roleObj.privileges,
                   {resource: {db: db.getName(), collection: "foo"}, actions: ['find', 'insert']});
assertHasPrivilege(roleObj.privileges,
                   {resource: {db: db.getName(), collection: ""}, actions: ['dropDatabase']});

// Update role
db.updateRole("roleA", {
    roles: ['roleB'],
    privileges: [{resource: {db: db.getName(), collection: "foo"}, actions: ['find']}]
});
roleObj = db.getRole("roleA", {showPrivileges: true});
assert.eq(1, roleObj.roles.length);
assertHasRole(roleObj.roles, "roleB", db.getName());
assert.eq(1, roleObj.privileges.length);
assertHasPrivilege(roleObj.privileges,
                   {resource: {db: db.getName(), collection: "foo"}, actions: ['find']});

// Test dropRole
db.dropRole('roleC');
assert.eq(null, db.getRole('roleC'));
roleObj = db.getRole("roleB", {showPrivileges: true});
assert.eq(0, roleObj.privileges.length);
assert.eq(0, roleObj.roles.length);

// Test dropAllRoles
db.dropAllRoles();
assert.eq(null, db.getRole('roleA'));
assert.eq(null, db.getRole('roleB'));
assert.eq(null, db.getRole('roleC'));
